/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cpproject1;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.EventObject;
import java.util.Random;
import javax.swing.event.EventListenerList;


public class Maze implements IMaze {
    private EventListenerList listenerList;
    private Flag f;
    private MazeCell[][] dungeon;
    private int NumberOfPlayers;
    private int sizeOfMaze;
    private volatile int flagStatus = 0;
    
    /**
     * 
     * @param sizeOfMaze
     * @param numberOfPlayers
     */
    public Maze(int sizeOfMaze, int numberOfPlayers) {
        this.listenerList = new EventListenerList();
        this.NumberOfPlayers = numberOfPlayers;
        this.sizeOfMaze = sizeOfMaze;
        dungeon = new MazeCell[this.sizeOfMaze][this.sizeOfMaze];
        for (int i = 0; i < this.sizeOfMaze; i++) {
            for (int j = 0; j < this.sizeOfMaze; j++) {
                this.dungeon[i][j] = new MazeCell();
            }
        }
        //this.CreatePlayers(); This reference escape so put it into startGAme
    }

    /**
     * 
     * @param listener
     */
    @Override
    public void subscribeToUIEvents(UpdateUIEventListener listener) {
        if (this.listenerList != null) {
            this.listenerList.add(UpdateUIEventListener.class, listener);
        }
    }

    /**
     * 
     */
    public void startGame() {
        this.f = new Flag(this);
        this.CreateDungeon();
        this.CreatePlayers();
        for (int i = 0; i < this.sizeOfMaze; i++) {
            for (int j = 0; j < this.sizeOfMaze; j++) {
                this.dungeon[i][j].x = i;
                this.dungeon[i][j].y = j;
                //checks if its a player
                if (isPlayer(i, j)) {
                    new Thread(this.dungeon[i][j].player, "Player " + i + "," + j).start();	//starting the player threads
                }
            }
        }
        new Thread(this.f, "FlagTimerThread").start();	//samratb: testing starting the flag thread
    }

    //checks if a valid player or not
    private boolean isPlayer(int i, int j) {
        if (this.dungeon[i][j].type == MazeCellType.Player || this.dungeon[i][j].type == MazeCellType.ZombiePlayer
                || this.dungeon[i][j].type == MazeCellType.FlagZombiePlayer
                || this.dungeon[i][j].type == MazeCellType.FlagPlayer) {
            return true;
        }

        return false;
    }

    //Getting a cell location which is of Path Type
    private Point getSafePlayerLocation() {
        Point retVal = new Point();
        Random random = new Random();
        int tempX, tempY;
        do {
            tempX = random.nextInt(this.sizeOfMaze);
            tempY = random.nextInt(this.sizeOfMaze);
            if (this.dungeon[tempX][tempY].type == MazeCellType.Path) {
                retVal.set(tempX, tempY);
            }
        } while (retVal.getX() == -1);
        return retVal;
    }

    //Player initialization for the first time
    /**
     * 
     */
    public void CreatePlayers() {
        for (int i = 0; i < this.NumberOfPlayers; i++) {
            Point p = this.getSafePlayerLocation();
            Player player = new Player(this, this.dungeon[p.getX()][p.getY()]); //this is fully constructed, no escape
            this.dungeon[p.getX()][p.getY()].type = MazeCellType.Player;
            this.dungeon[p.getX()][p.getY()].player = player;
            if (i == this.NumberOfPlayers - 1) {
                this.dungeon[p.getX()][p.getY()].type = MazeCellType.ZombiePlayer;
            }
        }
        Point p = this.getSafePlayerLocation();
        this.dungeon[p.getX()][p.getY()].type = MazeCellType.Flag;
    }

    //Reading dungeon from file
    private void CreateDungeon() {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(Constants.dungeonFile));
            String line;
            int lineNumber = 0;
            while ((line = reader.readLine()) != null && !line.isEmpty()
                    && lineNumber < this.sizeOfMaze) {
                int columnNumber = 0;
                String[] paths = line.split("0");
                for (int i = 0; i < paths.length; i++) {
                    String path = paths[i];
                    int j = 0;
                    while (j < path.length() && columnNumber < this.sizeOfMaze) {
                        this.dungeon[lineNumber][columnNumber].type = MazeCellType.Path;
                        columnNumber++;
                        j++;
                    }
                    if (columnNumber < this.sizeOfMaze) {
                        this.dungeon[lineNumber][columnNumber].type = MazeCellType.Wall;
                        columnNumber++;
                    }
                }
                lineNumber++;
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (Exception e) {
                    //eat it
                }
            }
        }
        this.raiseUpdateUIEvent();
    }

    //Called when FlagTimer determines a player is having a flag for more than WinTime
    /**
     * 
     */
    @Override
    public void weHaveAWinner() {
        System.out.println("We have a winner.");
        flagStatus = 3;
        
        //notifyallthreads to stop and kill themselves
        for (int i = 0; i < this.sizeOfMaze; i++) {
            for (int j = 0; j < this.sizeOfMaze; j++) {
                if (this.isPlayer(i, j)) {
                    this.dungeon[i][j].player.stop();
                }
            }
        }
        this.raiseUpdateUIEvent();
    }
    
    /**
     * 
     * @param direction
     * @param player
     * @return
     */
    @Override
    public synchronized Boolean Move(Directions direction, Player player) {
        int newX = player.playerLocation.x, newY = player.playerLocation.y;
        switch (direction) {
            case Left:
                newY--;
                break;
            case Right:
                newY++;
                break;
            case Up:
                newX--;
                break;
            case Down:
                newX++;
                break;
        }
        return this.moveToNewCell(newX, newY, player);
    }

    /**
     * 
     * @param direction
     * @param player
     * @return
     */
    @Override
    public synchronized Boolean MoveJump(Directions direction, Player player) {
        int newX = player.playerLocation.x, newY = player.playerLocation.y;
        switch (direction) {
            case Left:
                newY -= 2;
                break;
            case Right:
                newY += 2;
                break;
            case Up:
                newX -= 2;
                break;
            case Down:
                newX += 2;
                break;
        }
        return this.moveToNewCell(newX, newY, player);
    }

    /**
     * 
     * @return
     */
    @Override
    public synchronized MazeCellType[][] getDungeon() {
        MazeCellType[][] retVal = new MazeCellType[this.sizeOfMaze][this.sizeOfMaze];
        //taking a snapshot of maze for UI
        for (int i = 0; i < this.sizeOfMaze; i++) {
            for (int j = 0; j < this.sizeOfMaze; j++) {
                retVal[i][j] = this.dungeon[i][j].type;
            }
        }
        return retVal;
    }

    //not synchronized as called by sync functions and also not exposed as public
    private Boolean moveToNewCell(int newX, int newY, Player player) {
        //missing the maze boundary condition
        if (newX < 0 || newY < 0 || newY >= this.sizeOfMaze || newX >= this.sizeOfMaze) {
            return false;
        }
        MazeCell newCell = this.dungeon[newX][newY];
        //If our player steps on a non-Zombie player with flag
        if (newCell.type == MazeCellType.FlagPlayer) {
            System.out.println("flag captured by other player");
            flagStatus = 2;
            this.f.setPlayer(player);
            newCell.type = MazeCellType.Player;	//giving away of flag
            if (player.playerLocation.type == MazeCellType.Player) {
                player.playerLocation.type = MazeCellType.FlagPlayer;
            } else if (player.playerLocation.type == MazeCellType.ZombiePlayer) {
                player.playerLocation.type = MazeCellType.FlagZombiePlayer;
            }
            return false;
        } 
        //If the player steps on Zombie player, game reset
        else if (newCell.type == MazeCellType.ZombiePlayer 
                || newCell.type == MazeCellType.FlagZombiePlayer) {
            resetMaze();
            return true;
        } 
        //If the player wanted to move to a cell with wall or an Player in it
        else if (newCell.type == MazeCellType.Wall 
                || newCell.type == MazeCellType.Player) {
            return false;
        }

        //newCell ready for move
        if (newCell.type == MazeCellType.Flag) {
            System.out.println("flag captured");
            flagStatus = 1;
            if (player.playerLocation.type == MazeCellType.Player) {
                player.playerLocation.type = MazeCellType.FlagPlayer;
            } else if (player.playerLocation.type == MazeCellType.ZombiePlayer) {
                player.playerLocation.type = MazeCellType.FlagZombiePlayer;
            }
            this.f.setPlayer(player);
        }
        newCell.player = player;
        player.playerLocation.player = null;
        newCell.type = player.playerLocation.type;
        player.playerLocation.type = MazeCellType.Path;
        newCell.player.playerLocation = newCell;
        this.raiseUpdateUIEvent();
        return true;
    }

    //Signalling UI to refresh view
    private void raiseUpdateUIEvent() {
        UpdateUIEventListener[] list 
                = this.listenerList.getListeners(UpdateUIEventListener.class);
        if (list == null || list.length == 0) {
            return;
        }
        for (int i = 0; i < list.length; i++) {
            list[i].updateUI(new EventObject(this.getDungeon()));
        }
    }

    private synchronized void resetMaze() {
        System.out.println("reset called");
        flagStatus = 4;
        int playerCount = 0;
        Point pArr[] = new Point[this.NumberOfPlayers];
        for (int i = 0; i < this.sizeOfMaze; i++) {
            for (int j = 0; j < this.sizeOfMaze; j++) {
                if (this.dungeon[i][j].type == MazeCellType.Flag) {
                    this.dungeon[i][j].type = MazeCellType.Path;
                }
                if (isPlayer(i, j)) {
                    Point p = this.getSafePlayerLocation();
                    Boolean skip = false;
                    for (int k = 0; k < playerCount; k++) {
                        if (pArr[k].equals(new Point(i, j))) {
                            skip = true;
                            break;
                        }
                    }
                    if (skip) {
                        continue;
                    }
                    pArr[playerCount] = p;
                    playerCount++;
                    //set every player to a new type as Playe
                    this.dungeon[i][j].type = MazeCellType.Player;
                    //Move player to a new valid location
                    //User random number function, to find out valid path
                    //newX and newY will be from random number function
                    moveToNewCell(p.getX(), p.getY(), dungeon[i][j].player);
                    //set the last player as Zombie player
                    if (playerCount == this.NumberOfPlayers) {
                        this.dungeon[p.getX()][p.getY()].type 
                                = MazeCellType.ZombiePlayer;
                        //break;
                    }
                }
            }
        }
        Point p = this.getSafePlayerLocation();
        this.dungeon[p.getX()][p.getY()].type = MazeCellType.Flag;

        //setting reference of Player with flag to null
        this.f.setPlayer(null);
    }
    
    /**
     * 
     * @return
     */
    public int getFlagStatus()
    {
        return flagStatus;
    }
    
    /**
     * 
     * @return
     */
    public Long getElapsedTime()
    {
        if (f == null)
            return new Long(0);
        return new Long(System.currentTimeMillis() - f.time);
    }

}
